/**
 * Copyright (c) 2013, George Zeakis<zeageorge@google.com>
 * This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * Description: Tree like (nested topics)....
 */
function Subup_Tree(_options){
    /*
     * todo: 
     *       * bale function topicNamesParser
     *       * Bale subscribersCounter & publishersCounter gia ka8e topic 3exwrista alla kai genika ta synola autwn
     *       * callbacksCounter gia ka8e topic 3exwrista
     */
    // Helpers
    var Tools = function (){
        this.isString = function (str){ return (str && typeof str==='string')?true:false;};
        this.identifyMyName = function(){ return (this.identifyMyName.caller && this.identifyMyName.caller.name)?this.identifyMyName.caller.name:''; };
        this.getMyReference = function(){ return (this.getMyReference.caller)?this.getMyReference.caller:''; };
        this.isArray = function(o){ return Object.prototype.toString.call(o) === Object.prototype.toString.call([]); };
        this.logToConsole = function(){ if(window.console && window.console.log){window.console.log( Array.prototype.slice.call(arguments) );}; };
    };
    var tools = new Tools();
    
    var __Root = {}; // holds the Root topic objects as {topicName:topicObject}
    var __options = (_options)?_options:{ ns_separator:'.', topicNames_separator:' ', developmentMode:true};
    
    function __topicNode(name, callbacks, subscriber){
        var thisName = tools.identifyMyName();
        if (!name){ if (__options.developmentMode){tools.logToConsole(thisName+': error: you are trying to create a topicNode object, without a name.'); }; return false; };
        
        var _name=name, 
            _children={},
            _callbacks={},
            _options={default_priority:128, priority_min:1, priority_max:256},
            _thisReference=tools.getMyReference(),
            _thisCaller=(_thisReference!=='')?(_thisReference.caller)?_thisReference.caller:null:null,
            _subscriber=(subscriber)?subscriber:_thisCaller;
        
        function _subscribe(callbacks){
            var thisName = tools.identifyMyName();
            if (!callbacks){ 
                if (__options.developmentMode){
                    tools.logToConsole(thisName+': error: you are trying to subscribe to a topicNode object without providing any callbacks.'); 
                }; 
                return false; 
            };
            
            if (!tools.isArray(callbacks)){
                if (__options.developmentMode){tools.logToConsole(thisName+': error: callbacks must be an array of arrays.'); }; 
                return false; 
            };
            
            var _thisReference=tools.getMyReference(),
                _thisCaller=(_thisReference!=='')?(_thisReference.caller)?_thisReference.caller:null:null,
                _subscriber=_thisCaller;
            
            return _addCallbacks(callbacks, _subscriber);
        };
        function _addCallbacks(callbacks, subscriber){
            var thisName = tools.identifyMyName();
            if (!callbacks){ 
                if (__options.developmentMode){
                    tools.logToConsole(thisName+': error: you are trying to add callbacks to a topicNode object without providing any callbacks.'); 
                }; 
                return false; 
            };
            
            if (!tools.isArray(callbacks)){
                if (__options.developmentMode){tools.logToConsole(thisName+': error: callbacks must be an array of arrays.'); }; 
                return false; 
            };
            
            var _thisReference=tools.getMyReference(),
                _thisCaller=(_thisReference!=='')?(_thisReference.caller)?_thisReference.caller:null:null,
                _subscriber=(subscriber)?subscriber:_thisCaller;
            
            var c, l=callbacks.length;
            for (c=0; c<l; c++){
                if (!tools.isArray(callbacks[c])){
                    if (__options.developmentMode){
                        tools.logToConsole(thisName+': error: callback must be an array of [0]=function, [1]=context, [2]=priority.');
                    }; 
                    continue; 
                };
                
                if (callbacks[c].length<1){
                    if (__options.developmentMode){
                        tools.logToConsole(thisName+': error: callback length out of bounds. Must be at least 1 item in the array.');
                    }; 
                    continue; 
                };
                if (typeof callbacks[c][0]!=='function'){
                    if (__options.developmentMode){
                        tools.logToConsole(thisName+': error: Item [0] of the array must be a function.');
                    };
                    continue; 
                };
                
                if (typeof callbacks[c][1]==='undefined'){
                    callbacks[c][1]=_options.default_priority;
                };
                if (typeof callbacks[c][1]!=='number'){
                    if (__options.developmentMode){
                        tools.logToConsole(thisName+': error: Item [2] of the array, (field priority) must be a number (integer).'); 
                    };
                    continue; 
                };
                if (callbacks[c][1]<_options.priority_min || callbacks[c][1]>_options.priority_max){
                    if (__options.developmentMode){
                        tools.logToConsole(thisName+': error: Item [2] of the array, (field priority) must be a number (integer) in the range ['+_options.priority_min.toString()+' , '+_options.priority_max.toString()+'].'); 
                    }; 
                    continue; 
                };
                
                if (!callbacks[c][2]){ // if context of callback function is not set, then try to use the subscriber object as the context of the callback, else use the window object.
                    callbacks[c][2]=(_subscriber)?_subscriber:window;
                };
                
                if (typeof callbacks[c][2]!=='object' && typeof callbacks[c][2]!=='function'){ 
                    if (__options.developmentMode){
                        tools.logToConsole(thisName+': error: context must be an object not [',callbacks[c][2],']'); }; 
                    continue; 
                };
                
                if (typeof _callbacks[callbacks[c][1]]==='undefined'){ // if priority number does not exists, create it
                    _callbacks[callbacks[c][1]] = [];
                };
                
                _callbacks[callbacks[c][1]].push({ callback: callbacks[c][0], context : callbacks[c][2] });
            };
            
            if (__options.developmentMode){
                tools.logToConsole(thisName+': Topic ['+_name+'] registered callbacks: ',_callbacks);
            };
        };
        function _removeCallbacksByPriority(_priority){
            if (typeof _priority!=='number'){ return false; };
            if (_priority<_options.priority_min || _priority>_options.priority_max){ return false; };
            if (!_callbacks[_priority]){ return false; };
            
            _callbacks[_priority] = null;
            delete _callbacks[_priority];
            return true;
        };
        function _removeAllCallbacks(){ _callbacks=null; _callbacks = {}; };
        function _fire(_priority, _dataToPass_toCallbacks, publisher){
            var thisName = tools.identifyMyName();
            var _thisReference=tools.getMyReference(),
                _thisCaller=(_thisReference!=='')?(_thisReference.caller)?_thisReference.caller:null:null,
                _publisher=(publisher)?publisher:_thisCaller;
        
            function __runCallbacks_ofPriority(__priority, __publisher){
                var thisName = tools.identifyMyName();
                if (typeof __priority!=='number'){ return false; };
                var c, l=_callbacks[__priority].length;
                for (c=0; c<l; c++){
                    var cb = _callbacks[__priority][c].callback;
                    var cx = _callbacks[__priority][c].context;
                    try{
                        var ret = cb.call(cx,_dataToPass_toCallbacks, __publisher);
                        if (ret===false){ // if a callback return false, suspend all the other callbacks of the same priority
                            if (__options.developmentMode){tools.logToConsole(thisName+': notice: a callback returned false, suspending all other callbacks. Callback returned false, is: ', cb);};
                            return false; 
                        }; 
                    } catch(error){ 
                        if (__options.developmentMode){tools.logToConsole(thisName+': error: a callback failed to execute with error: ', error);};
                        continue;
                    };
                };
                
            };
            
            if (typeof _priority==='string'){
                if (_priority==='all' || _priority==='*'){
                    for (var i=_options.priority_min; i<=_options.priority_max; i++){
                        if (!_callbacks[i]){ continue; };
                        __runCallbacks_ofPriority(i, _publisher);
                    };
                } else { return false; };
            } else if (typeof _priority==='number'){
                if (!_callbacks[_priority]){
                    if (__options.developmentMode){tools.logToConsole(thisName+': error: priority ['+_priority.toString()+'] does not exists in the callbacks list');};
                    return false; 
                };
                __runCallbacks_ofPriority(_priority, _publisher);
            } else { 
                if (__options.developmentMode){tools.logToConsole(thisName+': error: invalid priority number.Must be a string=\'all\' or \'*\' , or a number (integer) ');};
                return false; 
            };
        };
        function _publish(args){
            var __myReference = tools.getMyReference();
            var __myCaller = (__myReference!=='')?(__myReference.caller)?__myReference.caller:null:null; 
            var __publisher = __myCaller;

            return _fire('*',args, __publisher);
        };
        
        function _addChild(name, callbacks){
            var _thisReference=tools.getMyReference(),
                _thisCaller=(_thisReference!=='')?(_thisReference.caller)?_thisReference.caller:null:null,
                _subscriber=_thisCaller;
        
            var c = new __topicNode(name, callbacks, _subscriber);
            if (c===false){ return false;};
            
            _children[c.name]=c;
            
            return c;
        };
        function _removeChild_byName(name){
            if (typeof name!=='string'){ return false;};
            if (!_children[name]){ return false;};
            
            _children[name]=null;
            delete _children[name];
            
            return true;
        };
        function _getChild_byName(name){
            if (typeof name!=='string'){ return false;};
            if (!_children[name]){ return false;};
            
            return _children[name];
        };
        function _removeAllChildren(){ _children=null; _children={}; };
        function _publishAllChildren(args){
            var __myReference = tools.getMyReference();
            var __myCaller = (__myReference!=='')?(__myReference.caller)?__myReference.caller:null:null; 
            var __publisher = __myCaller;
            
            for (var item in _children){
                if (Object.prototype.hasOwnProperty.call(_children, item)){
                    if (typeof _children[item]==='__topicNode'){
                        _children[item].fire('*',args, __publisher);
                    }
                };
            };
        };
        function _publishChild_byName(name, args){
            if (typeof name!=='string'){ return false;};
            var __myReference = tools.getMyReference();
            var __myCaller = (__myReference!=='')?(__myReference.caller)?__myReference.caller:null:null; 
            var __publisher = __myCaller;
            
            var c = _getChild_byName(name);
            
            if (c===false){return false; };
            c.fire('*',args,__publisher);
        };
        
        if (callbacks){ _addCallbacks(callbacks,_subscriber) };
        return {
            name:_name,
            addCallbacks:_addCallbacks,
            removeCallbacksByPriority:_removeCallbacksByPriority,
            removeAllCallbacks:_removeAllCallbacks,
            callbacks:_callbacks,
            options:_options,
            __caller:_thisCaller,
            children:_children,
            subscribe:_subscribe,
            publish:_publish,
            fire:_fire,
            
            addChild:_addChild,
            removeChild_byName:_removeChild_byName,
            getChild_byName:_getChild_byName,
            removeAllChildren:_removeAllChildren,
            publishAllChildren:_publishAllChildren,
            publishChild_byName:_publishChild_byName,
        };
    };
    
    function __subscribe(topic_names, callbacks){
        var thisName = tools.identifyMyName();
        if (!topic_names){ if (__options.developmentMode){tools.logToConsole(thisName+': error: you are trying to subscribe to a topic, without a name.'); }; return false; };
        if (!callbacks){ if (__options.developmentMode){tools.logToConsole(thisName+': error: you are trying to subscribe to a topic, without a callback function.'); }; return false; };
        
        var _thisReference=tools.getMyReference(),
            _thisCaller=(_thisReference!=='')?(_thisReference.caller)?_thisReference.caller:null:null,
            _subscriber=_thisCaller;
        
        if (tools.isArray(topic_names)){
            var c, l=topic_names.length;
            for (c=0; c<l; c++){
                if (typeof topic_names[c] === 'string'){
                    if (__Root[topic_names[c]]){ __Root[topic_names[c]].addCallbacks(callbacks,_subscriber);} else { __Root[topic_names[c]] = new __topicNode(topic_names, callbacks,_subscriber); };
                } else { if (__options.developmentMode){tools.logToConsole(thisName+': error: Array topic_names, item must be a string.'); }; continue;};
            };
            return true;
        } else if (typeof topic_names === 'string'){
            if (__Root[topic_names]){ 
                __Root[topic_names].addCallbacks(callbacks,_subscriber);
            } else { 
                __Root[topic_names] = new __topicNode(topic_names, callbacks,_subscriber); 
            };
            return __Root[topic_names];
        } else { if (__options.developmentMode){tools.logToConsole(thisName+': error: Topic name must be an array or string.'); }; return false; };
    };
    
    function __getTopicObject_ByName(_name){ return (_name)?(__Root[_name])?__Root[_name]:null:null; };
    
    function __publish(topic_names, args){
        var thisName = tools.identifyMyName();
        if (!topic_names){ if (__options.developmentMode){tools.logToConsole(thisName+': error: you are trying to publish a topic, without a name.'); }; return false; };
        
        var _thisReference=tools.getMyReference(),
            _thisCaller=(_thisReference!=='')?(_thisReference.caller)?_thisReference.caller:null:null,
            _publisher=_thisCaller;
        
        if (tools.isArray(topic_names)){
            var c, l=topic_names.length;
            for (c=0; c<l; c++){
                if (typeof topic_names[c] === 'string'){
                    if (__Root[topic_names[c]]){
                        __Root[topic_names[c]].fire('*', args, _publisher); 
                    } else {
                        if (__options.developmentMode){tools.logToConsole(thisName+': notice: Object ',_publisher,' wants to publish a topic ['+topic_names+'] that does not exists (no one has subscribed yet'); continue;};
                    };
                } else { if (__options.developmentMode){tools.logToConsole(thisName+': error: Array topic_names, item must be a string.'); }; continue;};
            };
        } else if (typeof topic_names === 'string'){
            if (__Root[topic_names]){
                __Root[topic_names].fire('*', args, _publisher);
                return __Root[topic_names];
            } else {
                if (__options.developmentMode){tools.logToConsole(thisName+': notice: Object ',_publisher,' wants to publish a topic ['+topic_names+'] that does not exists (no one has subscribed yet'); return null;};
            };
        } else { if (__options.developmentMode){tools.logToConsole(thisName+': error: Topic name must be an array or string.'); }; return false; };
    };

    return {
        subscribe:__subscribe,
        publish:__publish,
        getTopicObject_ByName:__getTopicObject_ByName,
    };
};